using System;
using System.Collections.Generic;
using System.Text;
using TTDotNet.Util;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;
using System.Drawing;

namespace TTDotNet.Engine.ThreeDEngine
{
    public class AJMeshH : AJObject
    {
        private Material[] _meshMaterials;
        private Texture[] _meshTextures;
        private Mesh _mesh;

        private string _meshFile;
        private VertexDeclaration instanceDeclaration;
        

        private Effect effect = null;
        private EffectHandle worldViewProjHandle, tex0Handle;

        private Random rng = new Random();
        private bool first = true;

        private AttributeRange[] range;

        private List<bool> visibleInstances;
        private List<Vector4> instancePositionList;
        private List<Vector4> instanceRotationList;

        private List<Vector4> instancePositionListVisible;
        private List<Vector4> instanceRotationListVisible;

        private VertexBuffer instancePositionBuffer;
        private VertexBuffer instanceRotationBuffer;
        private int vector4Size;

        private List<int> visibleOnes = new List<int>();

        private Dictionary<Guid, int> lookup;
        private bool viewableChanged = false;

        public AJMeshH(string meshPath, ObjectType type) : base(type)
        {
            vector4Size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vector4));

            _meshFile = meshPath;
            SetupMesh(Utility.FindMediaFile(meshPath));
            SetupEffect();

            CreateInstanceData();
            CreateDataBuffer(1);

            lookup = new Dictionary<Guid, int>();

            Game.gameWorld.Scene.AddObject(this); 
        }

        private void CreateInstanceData()
        {
            instancePositionList = new List<Vector4>();
            instanceRotationList = new List<Vector4>();
            instancePositionListVisible = new List<Vector4>();
            instanceRotationListVisible = new List<Vector4>();
            visibleInstances = new List<bool>();
        }

        private void CreateDataBuffer(int num)
        {
            if (instancePositionBuffer != null && instancePositionBuffer.Disposed == false)
            {
                instancePositionBuffer.Dispose();
            }
            if (instanceRotationBuffer != null && instanceRotationBuffer.Disposed == false)
            {
                instanceRotationBuffer.Dispose();
            }

            try
            {
                instancePositionBuffer = new VertexBuffer(typeof(Vector4), num, Game.device, Usage.WriteOnly, VertexFormats.Diffuse, Pool.Default);
                instanceRotationBuffer = new VertexBuffer(typeof(Vector4), num, Game.device, Usage.WriteOnly, VertexFormats.Diffuse, Pool.Default);
            }
            catch (Exception e)
            {

            }
        }

        private void SetupEffect()
        {
            ShaderFlags shaderFlags = ShaderFlags.None;
            string path = "data/ManagedInstancingHardware.fx";
            effect = ResourceCache.GetGlobalInstance().CreateEffectFromFile(Game.device,
                Utility.FindMediaFile(path), null, null, shaderFlags, null);

            worldViewProjHandle = effect.GetParameter(null, "WorldViewProjection");
            tex0Handle = effect.GetParameter(null, "Tex0");
        }

        private void SetupMesh(string path)
        {
            _meshFile = path;
            LoadResources();
        }

        private void LoadResources()
        {
            ExtendedMaterial[] materials = null;

            Game.device.RenderState.ZBufferEnable = true;

            try
            {
                _mesh = Mesh.FromFile(_meshFile, MeshFlags.Use32Bit, Game.device, out materials);
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show("Error Loading mesh: \n " + e.Message);
            }


            if (_meshTextures == null)
            {
                _meshTextures = new Texture[materials.Length];
                _meshMaterials = new Material[materials.Length];
                for (int i = 0; i < materials.Length; i++)
                {
                    _meshMaterials[i] = materials[i].Material3D;
                    _meshMaterials[i].Ambient = _meshMaterials[i].Diffuse;

                    if (materials[i].TextureFilename != null && (materials[i].TextureFilename != string.Empty))
                        _meshTextures[i] = TextureLoader.FromFile(Game.device, Utility.FindMediaFile(materials[i].TextureFilename));
                }
            }

            instanceDeclaration = new VertexDeclaration(Game.device, Declaration);
            range = _mesh.GetAttributeTable();

            //ScaleBy(new Vector3(0.75f, 0.75f, 0.75f));
        }

        private bool FrustumTest(AJCamera camera)
        {
            Rectangle view = GameGrid.viewableLand;
            bool changed = false;

            List<int> newList = new List<int>();

            for (int i = 0; i < instancePositionList.Count; i++)
            {
                Vector4 pos = instancePositionList[i];

                bool check = (pos.X >= view.Left && pos.X < view.Right && pos.Z >= view.Top && pos.Z < view.Bottom);
                //checked

                if (check)
                {
                    bool checkViewFrustum = camera.IsInViewFrustum(pos.X, pos.Y, pos.Z, 1f);
                    checkViewFrustum = true;

                    if (checkViewFrustum)
                    {
                        visibleInstances[i] = true;
                        newList.Add(i);
                    }
                }
                else
                {
                    visibleInstances[i] = false;
                }
            }

            bool same = true;

            if (newList.Count == visibleOnes.Count)
            {
                for (int i = 0; i < newList.Count; i++)
                {
                    if (!newList[i].Equals(visibleOnes[i]))
                    {
                        changed = true;
                        break;
                    }
                    else
                    {
                        try
                        {
                            if (!instancePositionList[newList[i]].Equals(instancePositionListVisible[i]))
                            {
                                changed = true;
                                break;
                            }
                        }
                        catch (Exception e)
                        {

                        }
                    }

                }
            }
            else
            {
                changed = true;
            }

            instancePositionListVisible.Clear();
            instanceRotationListVisible.Clear();

            for (int i = 0; i < instanceRotationList.Count; i++)
            {
                if (visibleInstances[i])
                {
                    instancePositionListVisible.Add(instancePositionList[i]);
                    instanceRotationListVisible.Add(instanceRotationList[i]);
                }
            }

            visibleOnes = new List<int>(newList);

            return changed;

            //for (int i = goingOut.Count - 1; i >= 0; i--)
            //{
            //    instancePositionListVisible.Remove(instancePositionList[goingOut[i]]);
            //    instanceRotationListVisible.Remove(instanceRotationList[goingOut[i]]);
            //    viewableChanged = true;
            //}

            //for (int i = 0; i < comingIn.Count; i++)
            //{
            //    instancePositionListVisible.Add(instancePositionList[comingIn[i]]);
            //    instanceRotationListVisible.Add(instanceRotationList[comingIn[i]]);
            //    viewableChanged = true;
            //}

            

        }

        public override void Render(AJCamera camera)
        {
            bool rewrite = FrustumTest(camera);

            if (instancePositionBuffer.SizeInBytes < (instancePositionListVisible.Count * vector4Size))
            {
                int currentSize = instancePositionBuffer.SizeInBytes / vector4Size;
                CreateDataBuffer(Math.Max(instancePositionListVisible.Count, (int)((float)currentSize * 1.1f) + 1));
            }
            else if (instancePositionBuffer.SizeInBytes / 3 > (instancePositionListVisible.Count * vector4Size))
            {
                CreateDataBuffer(instancePositionListVisible.Count + 1);
            }

            if (rewrite)
            {
                //Console.WriteLine("REWRITE!");

                instancePositionBuffer.Lock(0, 0, LockFlags.None);
                instancePositionBuffer.SetData(instancePositionListVisible.ToArray(), 0, LockFlags.None);
                instancePositionBuffer.Unlock();

                instanceRotationBuffer.Lock(0, 0, LockFlags.None);
                instanceRotationBuffer.SetData(instanceRotationListVisible.ToArray(), 0, LockFlags.None);
                instanceRotationBuffer.Unlock();
            }

            Game.device.RenderState.CullMode = Cull.None;

            if (instancePositionListVisible.Count > 0)
            {
                Game.device.VertexDeclaration = instanceDeclaration;

                Game.device.SetStreamSourceFrequency(0, (1 << 30) | instancePositionListVisible.Count);
                Game.device.SetStreamSource(0, _mesh.VertexBuffer, 0, _mesh.NumberBytesPerVertex);

                Game.device.SetStreamSourceFrequency(1, (2 << 30) | 1);
                Game.device.SetStreamSource(1, instancePositionBuffer, 0, 16);  

                Game.device.SetStreamSourceFrequency(2, (2 << 30) | 1);
                Game.device.SetStreamSource(2, instanceRotationBuffer, 0, 16);

                Game.device.Indices = _mesh.IndexBuffer;

                effect.SetValue(worldViewProjHandle, Matrix.Scaling(myScaling) * camera.View * camera.Projection);

                for (int ar = 0; ar < range.Length; ar++)
                {

                    effect.SetValue(tex0Handle, _meshTextures[ar]);

                    int numPasses = effect.Begin(FX.None);

                    for (int pass = 0; pass < numPasses; pass++)
                    {
                        effect.BeginPass(pass);

                        Game.device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, range[ar].VertexStart, range[ar].VertexCount, range[ar].FaceStart * 3, range[ar].FaceCount);

                        effect.EndPass();
                    }

                    effect.End();

                }

                Game.device.SetStreamSourceFrequency(0, 1);
                Game.device.SetStreamSourceFrequency(1, 1);
                Game.device.SetStreamSourceFrequency(2, 1);

            }
            else
            {
                //Console.WriteLine("0 done");
            }
            

        }

        public Guid AddNewInstance(Vector3 position, Vector3 rotation)
        {
            Guid newGuid = Guid.Empty;

            instancePositionList.Add(new Vector4(position.X, position.Y, position.Z, 0));
            instanceRotationList.Add(new Vector4(Geometry.DegreeToRadian(rotation.X), Geometry.DegreeToRadian(rotation.Y), Geometry.DegreeToRadian(rotation.Z), 0));
            visibleInstances.Add(false);

            byte[] bytes = new byte[16];
            rng.NextBytes(bytes);
            newGuid = new Guid(bytes);

            lookup[newGuid] = instancePositionList.Count - 1;
            
            return newGuid;
        }

        public void MoveInstance(Vector3 position, Guid instance)
        {
            if (lookup.ContainsKey(instance))
            {
                int item = lookup[instance];

                Vector4 exists = instancePositionList[item];

                exists.X = position.X;
                exists.Y = position.Y;
                exists.Z = position.Z;

                instancePositionList[item] = exists;
            }
        }

        public void RotateInstanceTo(Vector3 rotation, Guid instance)
        {
            if (lookup.ContainsKey(instance))
            {
                int item = lookup[instance];

                Vector4 exists = instanceRotationList[item];

                exists.X = Geometry.DegreeToRadian(rotation.X);
                exists.Y = Geometry.DegreeToRadian(rotation.Y);
                exists.Z = Geometry.DegreeToRadian(rotation.Z);


                instanceRotationList[item] = exists;
            }
        }

        public override ObjectInstance Pick(Vector3 rayStart, Vector3 rayDirection, AJCamera camera, out IntersectInformation intInf)
        {
            ObjectInstance nearest = new ObjectInstance(null, Guid.Empty);

            bool check = false;
            intInf = new IntersectInformation();

            Vector3 pos3 = Vector3.Empty;

            // FIX TO FIND THE NEAREST!!!1
            foreach (Guid guid in lookup.Keys)
            {
                Vector4 pos = instancePositionList[lookup[guid]];
                Vector4 rot = instanceRotationList[lookup[guid]];

                pos3.X = pos.X;
                pos3.Y = pos.Y;
                pos3.Z = pos.Z;

                // Convert ray to model space
                Matrix World =
                    Matrix.RotationYawPitchRoll(Geometry.DegreeToRadian(rot.Y), Geometry.DegreeToRadian(rot.X),
                                                    Geometry.DegreeToRadian(rot.Z)) *
                    Matrix.Translation(pos3);

                Matrix inverseWorld = Matrix.Invert(World);

                Vector3 localStart = Vector3.TransformCoordinate(rayStart, inverseWorld);
                Vector3 localDirection = Vector3.TransformNormal(rayDirection, inverseWorld);
                localDirection.Normalize();

                // Check for mesh intersection
                check = _mesh.Intersect(localStart, localDirection, out intInf);
                if (check)
                {
                    nearest = new ObjectInstance(this, guid);
                }
            }



            return nearest;
        }

           

        public static readonly VertexElement[] Declaration = new VertexElement[]
		{
			new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
			new VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0),
			new VertexElement(0, 24, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
			new VertexElement(1, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Color, 0),
            new VertexElement(2, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Depth, 0),
			VertexElement.VertexDeclarationEnd
	    };


        
    }

    public struct InstancedMesh
    {
        private AJMeshH mesh;
        private Guid instanceID;

        public InstancedMesh(AJMeshH ajmesh, Guid iID)
        {
            mesh = ajmesh;
            instanceID = iID;
        }

        public AJMeshH Mesh
        {
            get { return mesh; }
        }

        public Guid GUID
        {
            get { return instanceID; }
        }
    }

    
}
